package cn.bugstack.mybatis.builder.annotation;

import cn.bugstack.mybatis.annotations.Delete;
import cn.bugstack.mybatis.annotations.Insert;
import cn.bugstack.mybatis.annotations.Select;
import cn.bugstack.mybatis.annotations.Update;
import cn.bugstack.mybatis.bindings.MapperMethod;
import cn.bugstack.mybatis.builder.MapperBuilderAssistant;
import cn.bugstack.mybatis.mapping.SqlCommandType;
import cn.bugstack.mybatis.mapping.SqlSource;
import cn.bugstack.mybatis.scripting.LanguageDriver;
import cn.bugstack.mybatis.session.Configuration;
import cn.bugstack.mybatis.session.ResultHandler;
import cn.bugstack.mybatis.session.RowBounds;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
 * 基于注解的Mapper构建器
 * @author xingshujing
 */
public class MapperAnnotationBuilder {
    private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<>();

    private MapperBuilderAssistant assistant;
    private final Configuration configuration;
    private final Class<?> mapperInterface;

    public MapperAnnotationBuilder(Configuration configuration, Class<?> mapperInterface) {
        String resource = mapperInterface.getName().replace(".", "/") + ".java (best guess)";
        this.assistant = new MapperBuilderAssistant(configuration, resource);

        this.configuration = configuration;
        this.mapperInterface = mapperInterface;

        sqlAnnotationTypes.add(Select.class);
        sqlAnnotationTypes.add(Insert.class);
        sqlAnnotationTypes.add(Update.class);
        sqlAnnotationTypes.add(Delete.class);
    }

    public void parse() throws Exception {
        String resource = mapperInterface.toString();
        if (!configuration.isResourceLoaded(resource)) {
            configuration.addLoadedResource(resource);
            assistant.setCurrentNamespace(mapperInterface.getName());

            Method[] methods = mapperInterface.getMethods();
            for (Method method : methods) {
                if (!method.isBridge()) {
                    // 解析语句
                    parseStatement(method);
                }
            }

        }
    }

    private void parseStatement(Method method) throws Exception {
        Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
        if (sqlAnnotationType != null) {
            // 1. parameterType
            Class<?> parameterTypeClass = getParameterType(method);

            // 2. sqlSource
            Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
            //注解的属性在编译后的元数据中是以方法形式存在的，所以通过反射API访问时需要使用getMethod("value")来获取对应的方法，然后通过invoke()调用来获取其值。
            final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);

            StringBuilder sql = new StringBuilder();
            for (String fragment : strings) {
                sql.append(fragment);
                sql.append(" ");
            }
            LanguageDriver defaultDriver = configuration.getLanguageRegistry().getDefaultDriver();


            SqlSource sqlSource = defaultDriver.createSqlSource(configuration, sql.toString(), parameterTypeClass);

            if (sqlSource != null) {
                // 获取sql
                // 构造sqlSource
                String id = mapperInterface.getName()+"."+method.getName();

                SqlCommandType sqlCommandType = SqlCommandType.valueOf(sqlAnnotationType.getSimpleName().toUpperCase());

                boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
                String resultMapId = null;
                if (isSelect) {
                    resultMapId = parseResultMap(method);
                }
                assistant.addMappedStatement(id,
                        sqlSource,
                        sqlCommandType,
                        parameterTypeClass,
                        resultMapId,
                        getReturnType(method),
                        defaultDriver
                );
            }

        }
    }

    private String parseResultMap(Method method) {
        // generateResultMapName
        StringBuilder suffix = new StringBuilder();
        for (Class<?> c : method.getParameterTypes()) {
            suffix.append("-");
            suffix.append(c.getSimpleName());
        }
        if (suffix.length() < 1) {
            suffix.append("-void");
        }
        String resultMapId = mapperInterface.getName() + "." + method.getName() + suffix;

        // 添加 ResultMap
        Class<?> returnType = getReturnType(method);
        assistant.addResultMap(resultMapId, returnType, new ArrayList<>());
        return resultMapId;
    }

    /**
     * 重点：DAO 方法的返回类型，如果为 List 则需要获取集合中的对象类型
     */
    private Class<?> getReturnType(Method method) {
        Class<?> returnType = method.getReturnType();
        if (Collection.class.isAssignableFrom(returnType)) {
            // 获取泛型类型
            Type returnTypeParameter = method.getGenericReturnType();
            if (returnTypeParameter instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();
                if (actualTypeArguments != null && actualTypeArguments.length == 1) {
                    returnTypeParameter = actualTypeArguments[0];
                    if (returnTypeParameter instanceof Class) {
                        returnType = (Class<?>) returnTypeParameter;
                    } else if (returnTypeParameter instanceof ParameterizedType) {
                        // (issue #443) actual type can be a also a parameterized type
                        returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
                    } else if (returnTypeParameter instanceof GenericArrayType) {
                        Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
                        // (issue #525) support List<byte[]>
                        returnType = Array.newInstance(componentType, 0).getClass();
                    }
                }
            }
        }
        return returnType;
    }

    /**
     * 只有一个参数，那么parameterType就是该参数类型
     * 如果多于一个参数，那么parameterType就是ParamMap
     * @param method
     * @return
     */
    private Class<?> getParameterType(Method method) {
        Class<?> parameterType = null;
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (Class<?> clazz : parameterTypes) {
            if (!RowBounds.class.isAssignableFrom(clazz) && !ResultHandler.class.isAssignableFrom(clazz)) {
                if (parameterType == null) {
                    parameterType = clazz;
                } else {
                    parameterType = MapperMethod.ParamMap.class;
                }
            }
        }
        return parameterType;
    }


    private Class<? extends Annotation> getSqlAnnotationType(Method method) {
        for (Class<? extends Annotation> type : sqlAnnotationTypes) {
            Annotation annotation = method.getAnnotation(type);
            if (annotation != null) return type;
        }
        return null;
    }
}
